﻿
/*
 * PsTFS is a set of PowerShell commands to manage and use Team Foundation Server. PowerShell 
 * effectiveness is combined with the power of TFS API to maximizes TFS capabilities.
 * 
 * Creator : Choulant Noham
 * Mail : choulant@gmail.com
 * Blog : http://choulant.blogspot.com
 * Aide : http://blogs.msdn.com/jmanning/archive/2006/03/27/562195.aspx
 * 
 */

using System;
using System.Collections.Generic;

using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.VersionControl.Common;


namespace PsTFS.Objects.Methodes
{
    /// <summary>
    /// Classe permettant de faire un diff entre deux élements
    /// </summary>
    public static partial class Methode
    {
        /// <summary>
        /// Permet de connaitre la différence entre deux éléments d'un serveur
        /// </summary>
        /// <example>
        /// static void Main(string[] args)
        /// {
        ///     if (args.Length != 2)
        ///     {
        ///         Console.Error.WriteLine("Usage: treediff itemspec itemspec");
        ///         Environment.Exit(1);
        ///     }
        /// 
        ///     try
        ///     {
        ///         ProcessDiffs(args[0], args[1]);
        ///     }
        ///     catch (TeamFoundationServerException e)
        ///     {
        ///         Console.Error.WriteLine(e.Message);
        ///         Environment.Exit(1);
        ///     }
        ///     catch (TreeDiffException e)
        ///     {
        ///         Console.Error.WriteLine(e.Message);
        ///         Environment.Exit(1);
        ///     }
        /// } 
        /// </example>
        /// <param name="lhsSpecStr">Element source</param>
        /// <param name="rhsSpecStr">Element desctination</param>
        /// <param name="information">Affichage des information</param>
        /// <param name="vcs">Service de code source</param>
        public static List<ItemDiff> ProcessDiffs(string lhsSpecStr, string rhsSpecStr, VersionControlServer vcs,bool information)
        {
            ItemSpec lhsSpec, rhsSpec;
            VersionSpec lhsVersion, rhsVersion;

            string lhsRoot = LoadItemAndVersionSpec(lhsSpecStr, out lhsSpec, out lhsVersion);
            string rhsRoot = LoadItemAndVersionSpec(rhsSpecStr, out rhsSpec, out rhsVersion);

            Console.WriteLine("comment: Downloading items for path {0}", lhsRoot);
            ItemSet lhsItems = vcs.GetItems(lhsSpec, lhsVersion, DeletedState.NonDeleted, ItemType.Any, false);

            Console.WriteLine("comment: Downloading items for path {0}", rhsRoot);
            ItemSet rhsItems = vcs.GetItems(rhsSpec, rhsVersion, DeletedState.NonDeleted, ItemType.Any, false);

            Console.WriteLine("comment: Diffing item sets");
            return DiffItemSets(lhsItems, rhsItems, lhsRoot, rhsRoot, information, lhsVersion, rhsVersion);

        }

        private static string LoadItemAndVersionSpec(string spec, out ItemSpec itemSpec, out VersionSpec versionSpec)
        {
            string fileName;
            VersionSpec[] versions;

            VersionSpec.ParseVersionedFileSpec(spec, null, out fileName, out versions);

            switch (versions.Length)
            {
                case 0:
                    versionSpec = VersionSpec.Latest;
                    break;
                case 1:
                    versionSpec = versions[0];
                    break;
                default:
                    throw new TreeDiffException(string.Format("Expected 0 or 1 version spec - found {0}", versions.Length));

            }
            if (!string.IsNullOrEmpty(fileName))
            {
                itemSpec = new ItemSpec(fileName, RecursionType.Full);
            }
            else
            {
                throw new TreeDiffException("Item name was null or empty");
            }

            return fileName;
        }


        private static List<ItemDiff> DiffItemSets(ItemSet lhs, ItemSet rhs, string lhsRoot, string rhsRoot, bool information, VersionSpec lhsVersion, VersionSpec rhsVersion)
        {
            List<ItemDiff> lp = new List<ItemDiff>();
            int same = 0;
            int lonly = 0;
            int ronly = 0;
            int diff = 0;

            Dictionary<string, Item> lhsItemHash = CreateHash(lhs, lhsRoot);
            Dictionary<string, Item> rhsItemHash = CreateHash(rhs, rhsRoot);

            foreach (string lhsPath in lhsItemHash.Keys)
            {
                Item lhsItem = lhsItemHash[lhsPath];
                Item rhsItem;

                if (rhsItemHash.TryGetValue(lhsPath, out rhsItem))
                {
                    // verify that the file types are the same and if it is a file that the contents are identical
                    // this could be extended to also verify the case of the item name has not changed and that the
                    // encoding has not changed.
                    if (lhsItem.ItemType == rhsItem.ItemType &&
                           ((lhsItem.ItemType == ItemType.File && EqualFileContents(lhsItem, rhsItem)) ||
                            lhsItem.ItemType == ItemType.Folder)
                        )
                    {
                        same++;
                    }
                    else
                    {
                        if(information)
                            Console.WriteLine("diff: \"{0}\" - \"{1}\"", lhsItem.ServerItem, rhsItem.ServerItem);
                        ItemDiff id = new ItemDiff()
                        {
                            DateCheckInL = lhsItem.CheckinDate,
                            DateCheckInR = rhsItem.CheckinDate,
                            Information = lhsItem.ServerItem + " - " + rhsItem.ServerItem,
                            TypeDiff = TypeDiff.Diff,
                            TypeItem = lhsItem.ItemType,
                            ItemL = lhsItem,
                            ItemR = rhsItem,
                            lhsVersion = lhsVersion,
                            rhsVersion = rhsVersion
                        };

                        lp.Add(id);
                        diff++;
                    }
                    // by removing the RHS items when we are done in this loop the rhsItem collection
                    // will contain the RHS orphans.
                    rhsItemHash.Remove(lhsPath);
                }
                else
                {
                    if (information)
                        Console.WriteLine("left-only: {0}", lhsItem.ServerItem);

                    ItemDiff id = new ItemDiff()
                    {
                        DateCheckInL = lhsItem.CheckinDate,
                        Information = lhsItem.ServerItem,
                        TypeDiff = TypeDiff.LeftOnly,
                        TypeItem = lhsItem.ItemType,
                        ItemL = lhsItem,
                        lhsVersion = lhsVersion
                    };

                    lp.Add(id);
                    lonly++;
                }
            }
            foreach (Item rhsItem in rhsItemHash.Values)
            {
                if (information)
                    Console.WriteLine("right-only: {0}", rhsItem.ServerItem);

                ItemDiff id = new ItemDiff()
                {
                    DateCheckInR = rhsItem.CheckinDate,
                    Information = rhsItem.ServerItem,
                    TypeDiff = TypeDiff.RightOnly,
                    TypeItem = rhsItem.ItemType,
                    ItemR = rhsItem,
                    rhsVersion = rhsVersion
                };

                lp.Add(id);
                ronly++;
            }

            Console.WriteLine("Compared {0} items", ronly + lonly + diff + same);
            Console.WriteLine("Same: {0}", same);
            Console.WriteLine("Diff: {0}", diff);
            Console.WriteLine("Right Only: {0}", ronly);
            Console.WriteLine("Left Only: {0}", lonly);

            return lp;
        }

        // taken from Buck's post http://blogs.msdn.com/buckh/archive/2006/04/06/project_diff.aspx
        static bool EqualFileContents(Item item1, Item item2)
        {
            if (item1.ContentLength != item2.ContentLength)
            {
                return false;
            }

            // If the two hash values have different lengths or both have a length of zero, 
            // the files are not the same.  The only time this would happen would be for 
            // files uploaded by clients that have FIPS enforcement enabled (rare). 
            // Those clients can't compute the MD5 hash, so it has a length of zero in that 
            // case.  To do this right with FIPS, the code would need to compare file 
            // contents (call item.DownloadFile()). 
            // For information on FIPS enforcement and MD5, see the following link. 
            // http://blogs.msdn.com/shawnfa/archive/2005/05/16/417975.aspx 
            if (item1.HashValue.Length != item2.HashValue.Length ||
                item1.HashValue.Length == 0)
            {
                return false;
            }

            for (int i = 0; i < item1.HashValue.Length; i++)
            {
                if (item1.HashValue[i] != item2.HashValue[i])
                    return false;
            }
            return true;
        }


        private static Dictionary<string, Item> CreateHash(ItemSet items, string root)
        {
            Dictionary<string, Item> itemHash =
                new Dictionary<string, Item>(items.Items.Length, StringComparer.OrdinalIgnoreCase);

            foreach (Item item in items.Items)
            {
                itemHash.Add(item.ServerItem.Substring(root.Length), item);
            }

            return itemHash;
        }
    }

    internal class TreeDiffException : Exception
    {
        public TreeDiffException(string msg)

            : base(msg)
        {

        }

    }

}